package com.aliware.tianchi;

import org.apache.dubbo.common.URL;

import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * 负载均衡扩展接口
 * 必选接口，核心接口
 * 此类可以修改实现，不可以移动类或者修改包名
 * 选手需要基于此类实现自己的负载均衡算法
 */
public class UserLoadBalance implements LoadBalance {
    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        int flag = doSelectInFreeInvoker(invokers);
        Invoker invoker = null;
        if (flag == -1) {
            invoker = doSelectLeastActive(invokers);
            return invoker;
        }
        else {
            return invokers.get(flag);
        }
    }

    private <T> int doSelectInFreeInvoker(List<Invoker<T>> invokers) {
        if (GatewayManager.LOAD_INFO.size() < invokers.size()) {
            for (int i = 0; i<invokers.size(); i++) {
                Invoker invoker = invokers.get(i);
                ProviderInfo providerInfo = GatewayManager.getProviderInfo(invoker);
                if (providerInfo != null) {
                }
                else {
                    return i;
                }
            }
        }
        return -1;
    }
    //新的负载均衡策略尝试,最小连接数
    public static final String NAME = "leastactive";
    private final Random random = new Random();
    protected <T> Invoker<T> doSelectLeastActive(List<Invoker<T>> invokers) {
        updateWight(invokers);
        filterLost(invokers);
        int [] weights = new int[invokers.size()];
        int length = invokers.size();
        int leastActive = -1;
        int leastCount = 0;
        int[] leastIndexs = new int[length];
        int totalWeight = 0;
        int firstWeight = 0;
        boolean sameWeight = true;
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            ProviderInfo providerInfo = GatewayManager.getProviderInfo(invoker);
            AtomicInteger availThread = new AtomicInteger((int)(providerInfo.getMax().get()-providerInfo.getAllReqCount().get()));
            int active = (int)providerInfo.getAllReqCount().get();
            int weight = providerInfo.getServerWeight();
            int avgTime = providerInfo.getAvgSpendTime();
            Double t = -Math.log(avgTime) + 9;
            t = weight * t;
            weight = new Double(t).intValue();
            weights[i] = weight;
            if (leastActive == -1 || active < leastActive) {
                leastActive = active;
                leastCount = 1;
                leastIndexs[0] = i;
                totalWeight = weight;
                firstWeight = weight;
                sameWeight = true;
            } else if (active == leastActive && availThread.get()>0) {
                leastIndexs[leastCount++] = i;
                totalWeight += weight;
                if (sameWeight && i > 0
                        && weight != firstWeight) {
                    sameWeight = false;
                }
            }
        }
        if (leastCount == 1) {
            return invokers.get(leastIndexs[0]);
        }
        if (!sameWeight && totalWeight > 0) {
            int offsetWeight = random.nextInt(totalWeight);
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexs[i];
                offsetWeight -= weights[i];
                if (offsetWeight <= 0) {
                    return invokers.get(leastIndex);
                }
            }
        }else{
            return invokers.get(leastIndexs[random.nextInt(leastCount)]);
        }
        return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
    }
    public <T> void filterLost(List<Invoker<T>> invokers) {
        Long now = System.currentTimeMillis();
        for (Invoker invoker : invokers) {
            ProviderInfo providerInfo = GatewayManager.getProviderInfo(invoker);
            Long last = providerInfo.getLastActiveTime().get();
            if (now - last > 5) {
                providerInfo.getLastActiveTime().set(now + 5);
                invokers.remove(invoker);
                return;
            }

        }
    }

    public <T> void updateWight(List<Invoker<T>> invokers) {
        if (GatewayManager.LOAD_INFO.size() <3) return;
        int max = 0;
        int maxIndex = -1;
        int min = 5000;
        int minIndex = -1;
        for (int i = 0; i < 3; i++) {
            GatewayManager.getProviderInfo(invokers.get(i)).setServerWeight(15);
            int len = GatewayManager.getProviderInfo(invokers.get(i)).getLen();
            if (len <= 100) return;
            if (len > max) {
                max = len;
                maxIndex = i;
            }
            if (len <min) {
                min = len;
                minIndex = i;
            }
        }
        if (max < 700) return;
        GatewayManager.getProviderInfo(invokers.get(maxIndex)).setServerWeight(20);
        GatewayManager.getProviderInfo(invokers.get(minIndex)).setServerWeight(10);
    }
}
